从程序员到数据科学家:SAS 编程基础 (13) | 您所在的位置:网站首页 › sas 语言 编译器 github › 从程序员到数据科学家:SAS 编程基础 (13) |
当然, SAS 里的 %INCLUDE 宏与 C/C++ 的宏 #INCLUDE 不同,它有如下一些特征: %INCLUDE 语句是一个系统宏函数,可以在出现在源文件的任意位置,除了数据步 DATALINES或 DATALINES4语句后面的数据行被当作数据看待,其它的部分都是当作源代码被处理! %INCLUDE 语句包含的源代码文件中,也可以嵌套出现 %INCLUDE 语句的源代码,指向另外一个子文件。这种机制可以实现树状的代码组织,但要记得不要循环包含。 %INCLUDE 语句是全局的非执行语句,所以不要企图在分支逻辑中使用 %INCLUDE 语句来实现条件包含。 %INCLUDE 语句在编译前自动引入代码,并不自动触发代码提交,除非主动执行。 %INCLUDE 语句是一个系统宏函数,可以在出现在源文件的任意位置,除了数据步 DATALINES或 DATALINES4语句后面的数据行被当作数据看待,其它的部分都是当作源代码被处理! %INCLUDE 语句包含的源代码文件中,也可以嵌套出现 %INCLUDE 语句的源代码,指向另外一个子文件。这种机制可以实现树状的代码组织,但要记得不要循环包含。 %INCLUDE 语句是全局的非执行语句,所以不要企图在分支逻辑中使用 %INCLUDE 语句来实现条件包含。 %INCLUDE 语句在编译前自动引入代码,并不自动触发代码提交,除非主动执行。 我们也可以采用 filename语句预先定义文件引用,然后在 %INCLUDE中调用该文件引用来包含文件: filenameinitdata "c:tempinitdata.sas"; %includeinitdata; procprintdata=mydata;run; 默认情况下并不会显示被包含文件的源代码,当我们需要在 SAS 日志中显示被包含的代码的源代码时,我们可以使用包含语句的 /source2选项。 %include"c:tempinitdata.sas"/source2; 运行上面的代码后,在输出日志中可以看到如下内容: NOTE: %INCLUDE(水平1)文件c:tempinitdata.sas 是文件c:tempinitdata.sas。 128 +data mydata; 129 + input x @@; 130 + infile datalines; 131 + datalines; 当我们需要包含多个文件时,可以使用空格或逗号将多个文件同时包含进来: %include"c:tempinitdata.sas","c:tempprintdata.sas"; 如果操作系统的文件系统支持聚合存储形式(比如文件目录),我们也可以使用 filename 语句指向该目录,然后用括号将多个文件名以空格或逗号分隔的形式包含进来,比如: filenameinitdata "c:temp"; %includeinitdata(initdata.sasprintdata.sas); 注意:在 SAS BASE 界面中我们可使用 INCLUDE 命令或 RECALL 命令来包含 SAS程序或者重新召回上次提交的源代码,但那个 INCLUDE 命令与这儿的 INCLUDE 宏并非同一概念。 程序中动态扩展代码 CALL EXECUTE 例程 在 SAS 程序中,我们不但可以直接写代码,也可以将字符串文本当作源代码去动态编译执行。CALL EXECUTE 例程的功能就是将其参数解析为 SAS 源代码,然后在本数据步内,或者在本数据步结束处执行解析出来的源代码。其语法如下: CALL EXECUTE (); 其中参数可以是一个包含宏调用或 SAS 语句的文本表达式或常量;比如引号括起来的字符串,数据步中的一个字符型变量,或者是其它可以解析为宏文本表达式或 SAS 语句的字符型表达式。 对于参数的解析,使用单引号和双引号包含具有不同的行为。如果参数是由双引号括起来的,则参数会在数据步编译之前就进行解析。如果参数是由单引号括起来的,则参数会在程序执行期间立即执行。 %letB=1; data_null_; put"Before Invoke"; tabname="sashelp.prdsale"; v=symget("B"); putv=; /*对宏变量进行赋值,会立即执行后执行下一步*/ callexecute( '%let B=2;'); v=symget("B"); putv=; put"After Invoke"; run; 系统输出结果为: Before Invoke v=1 v=2 After Invoke 但如果我们使用双引号括起该参数,系统输出将会不同。比如下面的代码输出 v = 都是 2,说明双引号参数确实在进入 DATA 步之前被解析执行。 %letB=1; data_null_; put"Before Invoke"; tabname="sashelp.prdsale"; v=symget("B"); putv=; callexecute( "%let B=2;"); v=symget("B"); putv=; put"After Invoke"; run; 系统输出结果为: Before Invoke v=2 v=2 After Invoke 那有同学会问,如果 call Execute 参数中既包含 SAS 宏代码,也包含 SAS 代码时, SAS 编译器该如何工作的呢? 考察如下代码: %letB=1; data_null_; put"Before Invoke"; tabname="sashelp.prdsale"; v=symget("B"); putv=; callexecute( '%let B=2; data _null_; b=symget("B"); put b=; run;'); v=symget("B"); putv=; put"After Invoke"; run; 系统输出结果如下,说明 % LET B = 2 ; 是执行到 call Execute 时才执行的。但解析出来的 SAS 代码,则被延迟到数据步运行结束时才调用。 Before Invoke v=1 v=2 After Invoke b=2 但如果我们把 CALL EXECUTE 参数的单引号和双引号对调一下,结果会是什么呢? %letB=1; data_null_; put"Before Invoke"; tabname="sashelp.prdsale"; v=symget("B"); putv=; callexecute( "%let B=2; data _null_; b=symget('B'); put b=; run;"); v=symget("B"); putv=; put"After Invoke"; run; 输出结果如下。可以看出 % LET B = 2 ; 在进入 DATA 步之前就被执行了;而解析出的 SAS 代码则被延迟当前步运行结束时才调用。也就说,不管是 CALL EXECUTE 参数是单引号还是双引号括起来的,其生成的 SAS 语句代码都是被延迟执行的。 Before Invoke v=2 v=2 After Invoke b=2 比如下面的代码,不管是单引号还是双引号,PROC PRINT 步总是被延迟执行: %macroprintds; proc print data=sashelp.class; run; %mend; data_null_; put"Before Invoke"; callexecute("%printds"); /* ‘%printds’ 亦同*/ put"After Invoke"; run; 上面的代码等价于: data_null_; put"Before Invoke"; put"After Invoke"; run; procprintdata=sashelp.class; run; 除了常量字符串,call execute也可接受字符型变量作为参数,比如: data_null_; put"Before Invoke"; code='%printds;'; callexecute(code);/*字符型变量作为参数*/ put"After Invoke"; run; 更多的时候,我们会将字符型表达式作为参数,用来实现在一个数据步中调用另一个宏函数时拼凑参数。比如: %macroprintds(ds=sashelp.class); proc print data=&ds; run; %mend; data_null_; put"Before Invoke"; tabname="sashelp.prdsale"; callexecute( '%printds(ds='|| tabname || ');'); put"After Invoke"; run; 动态执行外部SAS程序与系统命令 X全局语句、CALL SYSTEM 例程和 SYSTEM 函数 在 SAS 程序中,我们很方便地跟底层操作系统进行交互。其中X 语句就是在 SAS 当前会话中执行外部操作系统命令的全局语句。比如: x'md c:mycodes'; 你会发现执行X语句后会有一个 DOS 窗口并停留在屏幕上,如果我们不希望出现该黑乎乎的窗口怎么办?可以使用 DOS 命令连接符 & 并执行 EXIT 命令自动退出 DOS 命令窗口。 x'md c:mycodes & exit'; 利用 X 全局语句,我们可以将一个大型分析项目分解为若干子任务,然后在主程序中按需调用子任务来组织我们的代码。比如我们已经在c:tempinitdata.sas 中写好了准备数据的代码: libnamemylib "c:temp"; datamylib.mydata; inputx @@; infiledatalines; datalines; 1 3 5 7 9 2 4 6 8 10 run; 随后我们在主程序中需要使用该数据进行后续工作,我们可以调用 X 语句运行它来生成数据: x'SAS c:tempinitdata.sas & exit'; libnamemylib "c:temp"; procprintdata=mylib.mydata; run; 注意: X 语句是全局语句,如果 X 语句出现在数据步内时,它会在数据步编译完成后自动执行。因此 X 语句不能用于条件分支中执行。要实现条件执行系统命令必须使用 CALL SYSTEM 例程或 SYTEM 函数。 X 语句可以使用如下两个系统选项: XWAIT/NOXWAIT :用于控制 SAS 执行外部命令的 DOS 命令窗口是否需要人为输入 EXIT 命令才关闭;默认是选项是 XWAIT;NOXWAIT 意味着 DOS 命令窗口在命令执行后自动关闭。 XSYNC/NOXSYNC:用于控制 SAS 是否等待执行命令启动的外部应用执行完毕,即所谓的同步式和异步模式。同步模式意味着 SAS 会话会等待所执行的命令执行完毕后才返回 SAS ,为默认选项;异步模式则表示 SAS 不必等待外部应用执行完毕,直接返回。同步模式一般用来需要等待外部命令执行完毕生成文件或数据才能继续的场景。 XWAIT/NOXWAIT :用于控制 SAS 执行外部命令的 DOS 命令窗口是否需要人为输入 EXIT 命令才关闭;默认是选项是 XWAIT;NOXWAIT 意味着 DOS 命令窗口在命令执行后自动关闭。 XSYNC/NOXSYNC:用于控制 SAS 是否等待执行命令启动的外部应用执行完毕,即所谓的同步式和异步模式。同步模式意味着 SAS 会话会等待所执行的命令执行完毕后才返回 SAS ,为默认选项;异步模式则表示 SAS 不必等待外部应用执行完毕,直接返回。同步模式一般用来需要等待外部命令执行完毕生成文件或数据才能继续的场景。 上面两个选项可以组合出4种不同的情形,但前者是控制 DOS 命令窗口的行为,后者是控制 SAS 是否需要等待应用执行完毕。 除了 X 全局语句,SAS 还提供 CALL SYSTEM例程来调用外部系统命令。 CALL SYSTEM 与 X 全局语句的区别是它可以在数据步内被条件调用;另有 SYSTEM函数与该 CALL 例程类似,但 SYSTEM函数可以返回命令执行的状态码,CALL SYSTEM 和 SYSTEM函数执行命令的长度不得超过 1024 字符。 作者编写如下几行 SAS 代码可将文件系统 C:Windows 目录中的所有文件名收集到 files 数据集中供分析,且如果存在 system.ini 文件的话,SAS 将弹出 Windows 记事本程序打开该文件。 x'dir c:windows /b > c:tempfiles.txt'; datafiles; infile"c:tempfiles.txt"end=last; inputfilename $32.; iffilename="system.ini"then callsystem("notepad c:windowssystem.ini"); run; x'del c:tempfiles.txt /f'; 当然如果你乐意,你也可以编写如下的代码,在 datalines 数据行上增加任意的 DOS 命令序列, SAS 会自动逐个执行。 data_null_; inputcmdstr $32.; callsystem( cmdstr ); datalines; cd c: md mylib cd mylib md mydata run; 如果在纯粹的 SAS 宏代码或开型代码中,我们可以使用系统宏 % SYSEXEC 语句来执行操作系统命令; % SYSEXEC 执行命令的状态码返回值可以从系统宏变量 & SYSRC 中获取。比如: %SYSEXECDIR C:WINDOWS /B > C:TEMPFILES.TXT; %PUT&SYSRC; /*执行成功返回0*/ %SYSEXECNOTEPAD C:TEMPFILES.TXT; %SYSEXEC; 其中最后一个无参数调用会弹出 DOS 命令窗口等待用户输入,直到用户输入 EXIT 命令返回 SAS 会话。 注意:由于 SAS 可以运行在多种 Windows 或 Linux/Unix 操作系统上,我们可以使用系统宏变量 SYSSCP 和 SYSSCPL 来标识不同的操作系统,从而编写通用性较高的 SAS 代码。比如笔者的 SAS 环境上:%PUT &SYSSCP &SYSSCPL; /*WIN X64_7PR*/ 结语 SAS 代码的组织技巧包括静态文件包含,程序中动态扩展代码以及动态执行外部 SAS 程序等几种方式。 SAS 代码不必像 Java 类文件那样呆板,用户可以随意组织 SAS 源文件。当然,前面讲到的 SAS 宏函数, FCMP 函数都是程序内部的函数一级的组织手段。SAS 虽然没有面向对象的继承这一概念,但 SAS 中仍然有准面向对象的技术可以实现丰富的程序元素。 作者:巫银良 SAS北京研发中心商业智能和可视化分析产品部技术总监 分析行业资深专家,大数据可视化分析负责人, SAS北京研发中心商业智能和可视化分析产品部技术总监,资深商业智能技术专家。 @所有SAS用户,SAS微信写手招募! 欢迎大家积极投稿!!!与小伙伴分享您的独特见解和卓越技术。您的来稿一经采用,将有神秘大奖赠予!投稿邮箱:[email protected] 想跟文章作者直接沟通交流吗?长按下方左侧二维码,加入SAS用户小组交流群,与文章作者直接沟通交流,还在等什么,快来加入吧~~ 加入SAS用户小组交流群 关注PowerToKnow公众号 关于更多SAS编程基础,您可以阅读: 【】 【】 【】 【】 【】 【】 【】 【表达式】 【】 【】 【】 【】 更多关于SAS干货,您可以阅读: 【】 【】 【】 【】 【】 欲了解更多信息,请点击阅读原文,或访问SAS中国官网 www.sas.com.cn ↓↓↓ 关于更多SAS编程基础,您可以阅读: 【】 【】 【】 【】 【】 【】 【】 【表达式】 【】 【】 【】 【】 更多关于SAS干货,您可以阅读: 【】 【】 【】 【】 【】 欲了解更多信息,请点击阅读原文,或访问SAS中国官网 www.sas.com.cn ↓↓↓ (本文作者:巫银良,如有转载,请注明出处。) 订阅: 添加好友 搜号码【saschina】 查找公众账号【SAS数据分析】 如果您的微信是最新版本,也可以: 分享:返回搜狐,查看更多 |
CopyRight 2018-2019 实验室设备网 版权所有 |